'''

评估指标是用来量化评估机器学习模型性能的数量化指标。
这些指标帮助我们了解模型在特定任务上的表现如何，
根据问题的性质（分类、回归、聚类等），可以有所不同。
一些常见的评估指标包括：

分类指标：
    准确率 accuracy：衡量正确分类的实例比例。
    精确率 precision：衡量所有正预测中真正正例的比例。
    召回率（灵敏度） recall：衡量所有实际正例中真正正例的比例。
    F1分数 f1 score：精确率和召回率的调和平均数，在类别不平衡时特别有用。
    ROC AUC：接收器操作特征（ROC）曲线下的面积，在二元分类问题中很有用。
    混淆矩阵：提供实际类别与预测类别的表格表示。

回归指标：
    平均绝对误差（MAE）：预测值与实际值之间的绝对差值的平均值。
    均方误差（MSE）：预测值与实际值之间的平方差值的平均值。
    均方根误差（RMSE）：MSE的平方根。
    R平方（R²）分数：因变量可由自变量预测的方差比例。

聚类指标：
    轮廓系数：衡量对象与其所属簇内部其他对象的相似程度与其他簇的相似程度。
    戴维森-布尔丁指数：衡量每个簇与其最相似簇之间的平均相似度。
    调整兰德指数（ARI）：衡量真实和预测簇分配之间的相似度，校正了随机性。

其他指标：
    平均精度均值（MAP）：用于评估排名检索结果的信息检索中的指标。
    Cohen's Kappa：衡量两个评分者或分类器之间的一致性，校正了随机性。

这些指标帮助从业者比较不同的模型、调整超参数，并在机器学习流程中做出明智的决策。评估指标的选择取决于特定的问题和分析目标。
'''


# binary classification problem
# 准确率 例如要分类100张图片 90张对了的话准确率就是90%
def accuracy(y_true, y_pred):
    """
     Function to calculate accuracy
     :param y_true: list of true values
     :param y_pred: list of predicted values
     :return: accuracy score
     """
    # initialize a simple counter for correct predictions
    correct_counter = 0
    # loop over all elements of y_true
    # and y_pred "together"

    '''
    在这段代码中，使用了zip函数来同时迭代两个列表 y_true 和 y_pred。
    这样做的目的是为了在每次迭代中同时获取真实标签和预测标签，以便进行比较。
    zip函数会将两个列表中相同索引位置的元素一一对应地组合在一起，
    从而方便进行并行迭代，提高代码的可读性和效率。

    '''
    for yt, yp in zip(y_true, y_pred):
        if yt == yp:
            # if prediction is equal to truth, increase the counter
            correct_counter += 1
    # return accuracy
    # which is correct predictions over the number of samples
    return correct_counter / len(y_true)


# 也可以用scikit-learn来计算准确度
from sklearn import metrics

l1 = [0, 1, 1, 1, 0, 0, 0, 1]
l2 = [0, 1, 0, 1, 0, 1, 0, 0]
print(metrics.accuracy_score(l1, l2))

'''
但正确率在实践中不能正确地展示模型的性能，下面介绍精度
在二分类问题中，混淆矩阵（Confusion Matrix）是一种用于评估分类模型性能的常见工具。在混淆矩阵中，有四个重要的指标：

真正例（True Positive，TP）： 模型正确地预测了正类样本的数量。即模型将正类样本正确地分类为正类。
真负例（True Negative，TN）： 模型正确地预测了负类样本的数量。即模型将负类样本正确地分类为负类。
假正例（False Positive，FP）： 模型将负类样本错误地预测为正类的数量。即模型将负类样本错误地分类为正类。
假负例（False Negative，FN）： 模型将正类样本错误地预测为负类的数量。即模型将正类样本错误地分类为负类。

下面是实现方法
'''


def true_positive(y_true, y_pred):
    """
     Function to calculate True Positives
     :param y_true: list of true values
     :param y_pred: list of predicted values
     :return: number of true positives
     """
    # initialize
    tp = 0
    for yt, yp in zip(y_true, y_pred):
        if yt == 1 and yp == 1:
            tp += 1
    return tp


def true_negative(y_true, y_pred):
    """
     Function to calculate True Negatives
     :param y_true: list of true values
     :param y_pred: list of predicted values
     :return: number of true negatives
     """
    # initialize
    tn = 0
    for yt, yp in zip(y_true, y_pred):
        if yt == 0 and yp == 0:
            tn += 1
    return tn


def false_positive(y_true, y_pred):
    """
     Function to calculate False Positives
     :param y_true: list of true values
     :param y_pred: list of predicted values
     :return: number of false positives
     """
    # initialize
    fp = 0
    for yt, yp in zip(y_true, y_pred):
        if yt == 0 and yp == 1:
            fp += 1
    return fp


def false_negative(y_true, y_pred):
    """
     Function to calculate False Negatives
     :param y_true: list of true values
     :param y_pred: list of predicted values
     :return: number of false negatives
     """
    # initialize
    fn = 0
    for yt, yp in zip(y_true, y_pred):
        if yt == 1 and yp == 0:
            fn += 1
    return fn


# Accuracy Score = (TP + TN) / (TP + TN + FP + FN)

def accuracy_v2(y_true, y_pred):
    """
     Function to calculate accuracy using tp/tn/fp/fn
     :param y_true: list of true values
     :param y_pred: list of predicted values
     :return: accuracy score
     """
    tp = true_positive(y_true, y_pred)
    fp = false_positive(y_true, y_pred)
    fn = false_negative(y_true, y_pred)
    tn = true_negative(y_true, y_pred)
    accuracy_score = (tp + tn) / (tp + tn + fp + fn)
    return accuracy_score


print(accuracy_v2(l1, l2))

# Precision = TP / (TP + FP)
'''
Precision = TP / (TP + FP)
精确率衡量了模型在所有被预测为正例的样本中，有多少个是真正的正例。
'''


def precision(y_true, y_pred):
    """
     Function to calculate precision
     :param y_true: list of true values
     :param y_pred: list of predicted values
     :return: precision score
     """
    tp = true_positive(y_true, y_pred)
    fp = false_positive(y_true, y_pred)
    precision = tp / (tp + fp)
    return precision


'''
Recall = TP / (TP + FN)
Recall(召回率)用于评估模型在所有真实正例中正确识别出的比例。
Recall是一个重要的评估指标，尤其适用于那些对漏报很敏感的应用场景，
例如医学诊断或安全检测。在这些场景中，我们更关心尽可能地捕获到所有的真实正例，
即更高的Recall值，即使可能会导致一些误报（False Positives）。
'''


def recall(y_true, y_pred):
    """
     Function to calculate recall
     :param y_true: list of true values
     :param y_pred: list of predicted values
     :return: recall score
     """
    tp = true_positive(y_true, y_pred)
    fn = false_negative(y_true, y_pred)
    recall = tp / (tp + fn)
    return recall


'''
Precision和Recall经常一起使用，并且二者之间存在权衡关系。
如果只关注Precision，模型可能会过于谨慎，只预测非常确信的正例，导致漏掉一些真正的正例。
相反，如果只关注Recall，模型可能会变得过于激进，尝试捕获尽可能多的正例，但同时也会带来更多的误报。
因此，需要在Precision和Recall之间进行权衡，选择适合特定应用场景的模型。
'''

'''
precision-recall curve
Precision-Recall（PR）曲线是一种用于评估二元分类模型性能的图形化工具。它显示了在不同阈值下Precision和Recall之间的关系。
PR曲线的横轴是Recall（召回率），纵轴是Precision（精确率）。通常情况下，随着阈值的降低，Recall会增加而Precision会降低。
在PR曲线中，每个点对应一个特定的阈值下的Precision和Recall值。

PR曲线的形状和分布能够直观地展示模型在不同阈值下的表现。一个理想的PR曲线应该是向右上方弯曲的，表示在保持高Precision的同时实现高Recall。
换句话说，这样的曲线意味着模型能够在识别出大部分正例的同时尽可能少地出错。
另一方面，如果PR曲线是向下凹的或者呈现为水平线，表示模型的性能较差，无法在高Recall下保持足够的Precision。
精确率和召回率的取值范围都是从0到1，接近1的值表示性能更好。
'''
y_true = [0, 0, 0, 1, 0, 0, 0, 0, 0, 0,
          1, 0, 0, 0, 0, 0, 0, 0, 1, 0]

y_pred = [0.02638412, 0.11114267, 0.31620708,
          0.0490937, 0.0191491, 0.17554844,
          0.15952202, 0.03819563, 0.11639273,
          0.079377, 0.08584789, 0.39095342,
          0.27259048, 0.03447096, 0.04644807,
          0.03543574, 0.18521942, 0.05934905,
          0.61977213, 0.33056815]
precisions = []
recalls = []
# how we assumed these thresholds is a long story
thresholds = [0.0490937, 0.05934905, 0.079377,
              0.08584789, 0.11114267, 0.11639273,
              0.15952202, 0.17554844, 0.18521942,
              0.27259048, 0.31620708, 0.33056815,
              0.39095342, 0.61977213]

# for every threshold, calculate predictions in binary
# and append calculated precisions and recalls
# to their respective lists
for i in thresholds:
    temp_prediction = [1 if x >= i else 0 for x in y_pred]
    p = precision(y_true, temp_prediction)
    r = recall(y_true, temp_prediction)
    precisions.append(p)
    recalls.append(r)

'''
F1 score
F1 分数是一种衡量分类模型精确度的指标，尤其是在数据类别分布不均衡的情况下。它是精确率（Precision）和召回率（Recall）的调和平均数，
用于同时考虑精确率（P）和召回率（R），以获取单个性能指标。
计算公式是
F1 = 2 * P * R / (P + R)

'''


def f1(y_true, y_pred):
    """
     Function to calculate f1 score
     :param y_true: list of true values
     :param y_pred: list of predicted values
     :return: f1 score
     """
    p = precision(y_true, y_pred)
    r = recall(y_true, y_pred)
    score = 2 * p * r / (p + r)
    return score


# And from scikit learn for the same lists, we get
y_true = [0, 0, 0, 1, 0, 0, 0, 0, 0, 0,
          1, 0, 0, 0, 0, 0, 0, 0, 1, 0]
y_pred = [0, 0, 1, 0, 0, 0, 1, 0, 0, 0,
          1, 0, 0, 0, 0, 0, 0, 0, 1, 0]

print(f1(y_true, y_pred) == metrics.f1_score(y_true, y_pred))

'''
other crucial terms
The first one is TPR or True Positive Rate, which is the same as recall.
TPR = TP / (TP + FN)
TPR or recall is also known as sensitivity.
'''


def tpr(y_true, y_pred):
    """
     Function to calculate tpr
     :param y_true: list of true values
     :param y_pred: list of predicted values
     :return: tpr/recall
     """
    return recall(y_true, y_pred)


'''
And FPR or False Positive Rate, which is defined as:
FPR = FP / (TN + FP)
'''


def fpr(y_true, y_pred):
    """
 Function to calculate fpr
 :param y_true: list of true values
 :param y_pred: list of predicted values
 :return: fpr
 """
    fp = false_positive(y_true, y_pred)
    tn = true_negative(y_true, y_pred)
    return fp / (tn + fp)

'''
And 1 - FPR is known as specificity or True Negative Rate or TNR.
These are a lot of terms, but the most important ones out of these are only TPR and 
FPR.
'''

